package com.hjy.backend.commons;

import com.hjy.common.error.Error;

import java.util.HashMap;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 实现缓存
 * 缓存算法为 引用计数, 引用次数为 0 时释放资源
 *
 * @param <T>
 */
public abstract class AbstractCache<T> {
    private HashMap<Long, T> cache;                     // 实际缓存的数据
    private HashMap<Long, Integer> references;          // 元素的引用个数
    private HashMap<Long, Boolean> getting;             // 正在获取某资源的线程

    private int maxResource;                            // 缓存的最大缓存资源数
    private int count = 0;                              // 缓存中元素的个数
    private Lock lock;


    public AbstractCache(int maxResource) {
        this.maxResource = maxResource;
        cache = new HashMap<>();
        references = new HashMap<>();
        getting = new HashMap<>();
        // 可重入锁
        lock = new ReentrantLock();
    }

    /**
     * 从缓存中获取对象
     * 1. 若该对象正在被创建, 则等待, 直到缓存池中有了该对象
     * 2. 获取到资源, 引用数 + 1
     * 3. 若缓存池中没有该对象, 就创建
     *
     * @param key
     * @return
     */
    protected T get(long key) throws Exception {
        while (true) {
            lock.lock();
            if (getting.containsKey(key)) {
                // 请求的资源正在被其他线程获取, 等一下再获取
                lock.unlock();
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    continue;
                }
                continue;
            }

            if (cache.containsKey(key)) {
                // 找到资源了
                T obj = cache.get(key);
                // 被上层模块引用次数+1
                references.put(key, references.get(key) + 1);
                lock.unlock();
                return obj;
            }

            // 缓存满了
            if (maxResource > 0 && count == maxResource) {
                lock.unlock();
                throw Error.CacheFullException;
            }

            count++;
            getting.put(key, true);
            lock.unlock();
            break;
        }

        // 到这里说明缓存池里面没有缓冲, 让新创建的对象加入缓存
        T obj = null;
        try {
            // 创建一个新的对象
            obj = getForCache(key);
        } catch (Exception e) {
            lock.lock();
            count--;
            getting.remove(key);
            lock.unlock();
            throw e;
        }

        // 原子操作
        lock.lock();
        getting.remove(key);
        cache.put(key, obj);
        references.put(key, 1);
        lock.unlock();

        return obj;

    }


    /**
     * 释放资源, 若资源引用数为 0, 释放该资源
     *
     * @param key
     */
    protected void release(long key) {
        lock.lock();
        try {
            int ref = references.get(key) - 1;
            // 引用数为 0 ,需要释放这个资源
            if (ref == 0) {
                T obj = cache.get(key);
                releaseForCache(obj);
                references.remove(key);
                cache.remove(key);
                count--;
            } else {
                references.put(key, ref);
            }
        } finally {
            lock.unlock();
        }
    }


    /**
     * 关闭缓存池, 同时释放所有资源
     */
    protected void close() {
        lock.lock();
        try {
            Set<Long> keys = cache.keySet();
            for (Long key : keys) {
                T obj = cache.get(key);
                releaseForCache(obj);
                references.remove(key);
                cache.remove(key);
            }
        } finally {
            lock.unlock();
        }
    }

    /**
     * 当资源不在缓存时的获取行为
     */
    protected abstract T getForCache(long key) throws Exception;

    /**
     * 当资源被驱逐时的释放行为
     */
    protected abstract void releaseForCache(T obj);

}
